/*
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/**
 * $Id$
 *
 * Because what we need is yet *ANOTHER* serialisation scheme.
 *
 * @file protocols/internal/encode.c
 * @brief Functions to encode data in our internal structure.
 *
 * @copyright 2020 The FreeRADIUS server project
 * @copyright 2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
 */
#include <freeradius-devel/internal/internal.h>
#include <freeradius-devel/io/pair.h>
#include <freeradius-devel/io/test_point.h>
#include <freeradius-devel/util/net.h>
#include <freeradius-devel/util/proto.h>


static fr_internal_encode_ctx_t	default_encode_ctx = { };

/** We use the same header for all types
 *
 */

/** Encode the value of the value pair the cursor currently points at.
 *
 * @param dbuff		data buffer to place the encoded data in
 * @param da_stack	da stack corresponding to the value pair
 * @param depth		in da_stack
 * @param cursor	cursor whose current value is the one to be encoded
 * @param encode_ctx	encoder context
 *
 * @return	either a negative number, indicating an error
 * 		or the number of bytes used to encode the value
 */
static ssize_t internal_encode(fr_dbuff_t *dbuff,
			       fr_da_stack_t *da_stack, unsigned int depth,
			       fr_dcursor_t *cursor, void *encode_ctx)
{
	fr_dbuff_t			work_dbuff = FR_DBUFF(dbuff);
	fr_dbuff_marker_t		enc_field, len_field, value_field;
	fr_dbuff_t			value_dbuff;
	fr_dict_attr_t const		*da = da_stack->da[depth];
	fr_pair_t			*vp = fr_dcursor_current(cursor);
	bool				unknown = false, internal = false;

	ssize_t				slen;
	size_t				flen, vlen, mlen;

	uint8_t				buff[sizeof(uint64_t)];
	uint8_t				enc_byte = 0;
	fr_internal_encode_ctx_t	*our_encode_ctx = encode_ctx;

	if (!our_encode_ctx) our_encode_ctx = &default_encode_ctx;

	/*
	 *	Silently skip name only attributes if we're writing
	 *	to a database or cache.
	 */
	if (!our_encode_ctx->allow_name_only && vp->da->flags.name_only) {
		fr_dcursor_next(cursor);
		return 0;
	}

	FR_PROTO_STACK_PRINT(da_stack, depth);

	fr_dbuff_marker(&enc_field, &work_dbuff);

	/*
	 *	Advance past first encoding byte
	 */
	FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, 0x00);

	switch (vp->vp_type) {
	/*
	 *	Only leaf attributes can be tainted
	 */
	case FR_TYPE_LEAF:
		if (vp->vp_tainted) enc_byte |= FR_INTERNAL_FLAG_TAINTED;
		break;

	default:
		break;
	}

	/*
	 *	Need to use the second encoding byte
	 *
	 *	0                   1
	 *	0 1 2 3 4 5 6 7 8 9 0
	 * 	+-+-+-+-+-+-+-+-+-+-+
	 * 	|u|i|-|-|-|-|-|e|
	 * 	+-+-+-+-+-+-+-+-+-+-+
	 */
	if ((unknown = da->flags.is_unknown) ||
	    (internal = (da->parent == fr_dict_root(fr_dict_internal())))) {
		enc_byte |= FR_INTERNAL_FLAG_EXTENDED;
		FR_DBUFF_IN_BYTES_RETURN(&work_dbuff,
					 (unknown * FR_INTERNAL_FLAG_UNKNOWN) |
					 (internal * FR_INTERNAL_FLAG_INTERNAL));
	}

	/*
	 *	Encode the type and write the width of the
	 *	integer to the encoding byte.
	 */
	flen = fr_dbuff_in_uint64v(&work_dbuff, da->attr);
	if (flen <= 0) return flen;
	enc_byte |= ((flen - 1) << 5);

	/*
	 *	Leave one byte in hopes that the length will fit
	 *	so we needn't move the encoded data.
	 */
	fr_dbuff_marker(&len_field, &work_dbuff);
	FR_DBUFF_ADVANCE_RETURN(&work_dbuff, 1);

	/*
	 *	Create dbuff to hold encoded data--the fr_dbuff_move() done
	 *	if the length field needs more than one byte will guard
	 *	against insufficient space.
	 */
	value_dbuff = FR_DBUFF_BIND_CURRENT(&work_dbuff);
	fr_dbuff_marker(&value_field, &value_dbuff);

	switch (da->type) {
	case FR_TYPE_LEAF:
		slen = fr_value_box_to_network(&value_dbuff, &vp->data);
		if (slen < 0) return PAIR_ENCODE_FATAL_ERROR;
		fr_dcursor_next(cursor);
		break;

	/*
	 *	This is the vendor container.
	 *	For RADIUS it'd be something like attr 26.
	 *
	 *	Inside the VSA you then have the vendor
	 *	which is just encoded as another TLVish
	 *	type attribute.
	 *
	 *	For small vendor PENs <= 255 this
	 *	encoding is 6 bytes, the same as RADIUS.
	 *
	 *	For larger vendor PENs it's more bytes
	 *	but we really don't care.
	 */
	case FR_TYPE_VSA:
	case FR_TYPE_VENDOR:

	/*
	 *	Children of TLVs are encoded in the context
	 *	of the TLV.
	 *
	 *	STRUCTs are encoded as TLVs, because the struct
	 *	packing only applies to the original protocol, and not
	 *	to our internal encoding.
	 */
	case FR_TYPE_TLV:
	case FR_TYPE_STRUCT:
		/*
		 *	We've done the complete stack.
		 *	Hopefully this TLV has some
		 *	children to encode...
		 */
		if (da == vp->da) {
			fr_dcursor_t	children;
			fr_pair_t	*child;

			for (child = fr_pair_dcursor_init(&children, &vp->vp_group);
			     child;
			     child = fr_dcursor_current(&children)) {

				FR_PROTO_TRACE("encode ctx changed %s -> %s", da->name, child->da->name);

				fr_proto_da_stack_build_partial(da_stack, da_stack->da[depth], child->da);
				FR_PROTO_STACK_PRINT(da_stack, depth);

				slen = internal_encode(&value_dbuff, da_stack, depth + 1, &children, encode_ctx);
				if (slen < 0) return slen;
			}
			fr_dcursor_next(cursor);
			break;
		}

		/*
		 *	Still encoding intermediary TLVs...
		 */
		slen = internal_encode(&value_dbuff, da_stack, depth + 1, cursor, encode_ctx);
		if (slen < 0) return slen;
		break;

	/*
	 *	Each child of a group encodes from the
	 *	dictionary root to the leaf da.
	 *
	 *	Re-enter the encoder at the start.
	 *	We do this, because the child may
	 *      have a completely different da_stack.
	 */
	case FR_TYPE_GROUP:
	{
		fr_dcursor_t	children;
		fr_pair_t	*child;

		for (child = fr_pair_dcursor_init(&children, &vp->vp_group);
		     child;
		     child = fr_dcursor_current(&children)) {
		     	FR_PROTO_TRACE("encode ctx changed %s -> %s", da->name, child->da->name);

			slen = fr_internal_encode_pair(&value_dbuff, &children, encode_ctx);
			if (slen < 0) return slen;
		}
		fr_dcursor_next(cursor);
	}
		break;

	default:
		fr_strerror_printf("%s: Unexpected attribute type \"%s\"",
				   __FUNCTION__, fr_type_to_str(da->type));
		return PAIR_ENCODE_FATAL_ERROR;
	}

	/*
	 *	Encode the total length, and write the width
	 *	of the integer to the encoding byte.
	 *
	 *	Already did length checks at the start of
	 *	the function.
	 */
	vlen = fr_dbuff_used(&value_dbuff);
	flen = (ssize_t) fr_nbo_from_uint64v(buff, vlen);

	/*
	 *	Ugh, it's a long one, need to move the data.
	 */
	if (flen > 1) {
		fr_dbuff_advance(&value_field, flen - 1);
		fr_dbuff_set_to_start(&value_dbuff);
		mlen = fr_dbuff_move(&value_field, &value_dbuff, vlen);
		if (mlen < vlen) return -(vlen - mlen);
	}

	FR_DBUFF_IN_MEMCPY_RETURN(&len_field, buff, flen);
	enc_byte |= ((flen - 1) << 2);
	FR_DBUFF_IN_RETURN(&enc_field, enc_byte);

	FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff) - vlen, "header");

	FR_PROTO_HEX_DUMP(fr_dbuff_start(&value_dbuff), vlen, "value %s",
			  fr_type_to_str(vp->vp_type));

	return fr_dbuff_set(dbuff, &work_dbuff);
}

/** Encode a data structure into an internal attribute
 *
 * @param[in,out] dbuff		Where to write encoded data and how much one can write.
 * @param[in] cursor		Specifying attribute to encode.
 * @param[in] encode_ctx	Additional data such as the shared secret to use.
 * @return
 *	- >0 The number of bytes written to out.
 *	- 0 Nothing to encode (or attribute skipped).
 *	- <0 an error occurred.
 */
ssize_t fr_internal_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *encode_ctx)
{
	fr_pair_t		*vp;
	fr_da_stack_t		da_stack;

	vp = fr_dcursor_current(cursor);
	if (!vp) return 0;

	fr_proto_da_stack_build(&da_stack, vp->da);

	return internal_encode(dbuff, &da_stack, 0, cursor, encode_ctx);
}

/** Encode a list of pairs using the internal encoder
 *
 * @param[out] dbuff		Where to write encoded data.
 * @param[in] list		List of attributes to encode.
 * @param[in] encode_ctx	Additional data to be used by the encoder.
 * @return
 *	- length of encoded data on success
 *	- < 0 on failure
 */
ssize_t fr_internal_encode_list(fr_dbuff_t *dbuff, fr_pair_list_t const *list, void *encode_ctx)
{
	fr_pair_t		*vp;
	fr_dcursor_t		dcursor;
	ssize_t			ret = 0, len = 0;
	fr_da_stack_t		da_stack;

	for (vp = fr_pair_dcursor_init(&dcursor, list);
	     vp;
	     vp = fr_dcursor_current(&dcursor)) {
		fr_proto_da_stack_build(&da_stack, vp->da);
		ret = internal_encode(dbuff, &da_stack, 0, &dcursor, encode_ctx);
		if (ret < 0) return ret;
		len += ret;
	}

	return len;
}

/*
 *	Test points
 */
extern fr_test_point_pair_encode_t internal_tp_encode_pair;
fr_test_point_pair_encode_t internal_tp_encode_pair = {
	.test_ctx	= NULL,
	.func		= fr_internal_encode_pair
};
